// Script Manager
// (c) jimon game studio

#ifndef JEH_CSCRIPTMANAGER_ANGELSCRIPT
#define JEH_CSCRIPTMANAGER_ANGELSCRIPT

#include "IScriptManager.h"
#include <memory.h>
#include <angelscript.h>

namespace je
{
	namespace core
	{

		//! Script Object
		class CScriptObject_AngelScript:public je::core::IScriptObject
		{
		protected:
			asIScriptEngine * Engine;
		public:
			s32 TypeId;
			void * Ptr;

			//! Constructor
			CScriptObject_AngelScript(const jeStringc & Name,asIScriptEngine * engine)
				:Engine(engine)
			{
				TypeId = Engine->GetModule(0)->GetTypeIdByDecl(Name.c_str());
				Ptr = Engine->CreateScriptObject(TypeId );
			}

			//! Destructor
			~CScriptObject_AngelScript()
			{
				Engine->ReleaseScriptObject(Ptr,TypeId);
			}

			//! Return Pointer
			virtual void * GetPointer()
			{
				return Ptr;
			}
		};


		//! Script Manager
		class CScriptManager_AngelScript:public je::core::IScriptManager
		{
		protected:
			asIScriptEngine * Engine;
			asIScriptContext * ScriptContext;

			f64 timeOut;

			s32 ArgNum;

			class ClassRegEntry
			{
			public:
				jeStringc Name;
				s32 Size;
				ClassRegEntry * BaseClass;
				struct RegMethod
				{
					jeStringc Def;
					c8 * Ptr;
					s32 PtrSize;
					RegMethod(jeStringc def,jeScriptMethodPtrBase * ptr)
						:Def(def)
					{
						PtrSize = ptr->GetPtrSize();
						JENEW_A(Ptr,c8,PtrSize)
						#ifdef JE_MSVC
							memcpy_s(Ptr,PtrSize,ptr->GetPtrData(),PtrSize);
						#else
							memcpy(Ptr,ptr->GetPtrData(),PtrSize);
						#endif
					}

					~RegMethod()
					{
						JEDELETE_A(Ptr)
					}
				};
				struct RegVar
				{
					jeStringc Def;
					s32 Offset;
					RegVar(jeStringc def,s32 offset)
						:Def(def),Offset(offset)
					{}
				};
				typedef jeList<RegMethod*> MethodsListType;
				typedef jeList<RegVar*> VarsListType;
				MethodsListType Methods;
				VarsListType Vars;

				void RegisterMethodsAndVars(jeStringc SetClassName,asIScriptEngine * Engine)
				{
					for(MethodsListType::Iterator Iter=Methods.begin();Iter!=Methods.end();Iter++)
					{
						asSFuncPtr p;
						asMemClear(&p,sizeof(p));
						asMemCopy(&p,(*Iter)->Ptr,(*Iter)->PtrSize);
						p.flag = 3;
						Engine->RegisterObjectMethod(SetClassName.c_str(),(*Iter)->Def.c_str(),p,asCALL_THISCALL);
					}
					for(VarsListType::Iterator Iter=Vars.begin();Iter!=Vars.end();Iter++)
						Engine->RegisterObjectProperty(SetClassName.c_str(),(*Iter)->Def.c_str(),(*Iter)->Offset);

					if(BaseClass)
						BaseClass->RegisterMethodsAndVars(SetClassName,Engine);
				}
				ClassRegEntry()
					:BaseClass(NULL)
				{
				}
				~ClassRegEntry()
				{
					for(MethodsListType::Iterator Iter=Methods.begin();Iter!=Methods.end();Iter++)
						JEDELETE(*Iter)
					Methods.clear();
					for(VarsListType::Iterator Iter=Vars.begin();Iter!=Vars.end();Iter++)
						JEDELETE(*Iter)
					Vars.clear();
				}
			};


			typedef jeMap<jeStringc,ClassRegEntry*> ClassesMapType;
			ClassesMapType Registration;

		public:
			//! Constructor
			CScriptManager_AngelScript();

			//! Destructor
			~CScriptManager_AngelScript();

			//! Load Script
			u1 LoadScript(core::IBuffer * Buffer);

			//! Load Byte Code
			u1 LoadByteCode(core::IBuffer * Buffer);

			//! Save Byte Code
			core::IBuffer * SaveByteCode();

			//! Register Function
			void RegisterFunction(const jeStringc & Declaration,const jeScriptFunction & FunctionPtr);

			//! Register Class
			void RegisterClass(const jeStringc & ClassName,s32 ClassSize);

			//! Register Class
			void RegisterClassBase(const jeStringc & ClassName,const jeStringc & BaseClassName,s32 ClassSize);

			//! Register Class Method
			void RegisterClassMethod(const jeStringc & ClassName,const jeStringc & MethodDefinition,jeScriptMethodPtrBase * MethodPtr);

			//! Register Class Method Variable
			void RegisterClassVariable(const jeStringc & ClassName,const jeStringc & VariableDefinition,s32 VariableOffset);

			//! Register Global Variable
			void RegisterGlobalVariable(const jeStringc & Declaration,void * Pointer);

			//! Execute Command
			void ExecuteCommand(const jeStringc & Command);

			//! Gargabe Collector
			void GarbageCollector();

			//! Add Function Param
			void AddParam(u1 Param);

			//! Add Function Param
			void AddParam(c8 Param);

			//! Add Function Param
			void AddParam(s32 Param);

			//! Add Function Param
			void AddParam(f32 Param);

			//! Add Function Param
			void AddParam(void * Param);

			//! Call Internal
			u1 CallInternal(s32 CallId,void * ObjectPtr = NULL);

			//! Call Function,return true if successful
			u1 CallFunction(const jeStringc & FunctionName);

			//! Call Method,return true if successful
			u1 CallMethod(const jeStringc & MethodName,IScriptObject * Object);

			//! Execute Call
			u1 ExecuteCall();

			//! Get Result
			s8 GetResults8();

			//! Get Result
			s32 GetResults32();

			//! Get Result
			f32 GetResultf32();

			//! Get Result
			void * GetResultPointer();

			//! Create Script Object
			IScriptObject * CreateScriptObject(const jeStringc & Name);
		};
	}
}

#endif
